<?php
class CF_Acl_Resource
{
	/**
	 * 
	 * @var string
	 */
	protected $_id;
	
	/**
	 * 
	 * @var CF_Acl
	 */
	protected $_acl;
	
	/**
	 * 
	 * @var array
	 */
	protected $_privileges = array();
	
	/**
	 * @var array
	 */
	protected $_rules = array();
	
	/**
	 * @var boolean|CF::TYPE_EXCEPTION
	 */
	protected $_allowOnNotFoundRole = false;
	
	/**
	 * 
	 * @var boolean
	 */
	protected $_allowOnNotFoundPrivilege = false;
	
	/**
	 * 
	 * @param string $id
	 * @param CF_Acl $acl
	 * @return void
	 */
	public function __construct($id, CF_Acl $acl)
	{
		$this->_id = (string) $id;
		$this->_acl = $acl;
	}
	
	/**
	 * @return string
	 */
	public function getId()
	{
		return $this->_id;
	}
	
	/**
	 * 
	 * @param string $privilege
	 * @return boolean
	 */
	public function has($privilege) {
		return isset($this->_privileges[$privilege]);
	}
	
	/**
	 * @return array
	 */
	public function getAll()
	{
		return $this->_privileges;
	}
	
	/**
	 * 
	 * @param string|array|args $privileges
	 * @return CF_Acl_Resource $this
	 */
	public function add($privileges)
	{
		if(func_num_args() > 1){
			$privileges = func_get_args();
		}
		
		if(is_array($privileges)){
			foreach ($privileges as $privilege){
				$this->add($privilege);
			}
		}else{
			$privileges = (string) $privileges;
			$this->_privileges[$privileges] = $privileges;
		}
		
		return $this;
	}
	
	/**
	 * 
	 * @param string|array|args $privileges
	 * @return CF_Acl_Resource $this
	 */
	public function remove($privileges)
	{
		if(func_num_args() > 1){
			$privileges = func_get_args();
		}
		
		if(is_array($privileges)){
			foreach ($privileges as $privilege){
				$this->remove($privilege);
			}
		}else{
			$privileges = (string) $privileges;
			unset($this->_privileges[$privileges]);
		}
		
		return $this;
	}
	
	/**
	 * 
	 * @param boolean $type
	 * @param string|array|CF_Acl_Role $roles
	 * @param string|array $privileges [optional]
	 * @return CF_Acl_Resource $this
	 */
	public function setRule($type, $roles, $privileges = null)
	{
		$type = (boolean) $type;
		if(!is_array($roles)) $roles = array($roles);
		if(null !== $privileges && is_string($privileges)) $privileges = array($privileges);
		
		foreach ($roles as $role){
			$roleId = (string) $role;
			
			if(null === $privileges){
				$this->_rules[$roleId] = $type;
				continue;
			}
			
			foreach ($privileges as $privilege){
				$this->_rules[$roleId][$privilege] = $type;
			}
		}

		return $this;
	}
	
	/**
	 * 
	 * @param string|array|CF_Acl_Role $roles
	 * @param string|array $privileges
	 * @return CF_Acl_Resource::setRule()
	 */
	public function allow($roles, $privileges = null)
	{
		return $this->setRule(true, $roles, $privileges);
	}
	
	/**
	 * 
	 * @param string|array|CF_Acl_Role $roles
	 * @param string|array $privileges
	 * @return CF_Acl_Resource::setRule()
	 */
	public function deny($roles, $privileges = null)
	{
		return $this->setRule(false, $roles, $privileges);
	}
	
	protected function _isAllowed($role, $privileges)
	{
		$roleId = (string) $role;
		
		# if role does not exist on rule, check parent roles
		if(!isset($this->_rules[$roleId])){
			$parent = $role->getAllParents();
			if(true !== ($res = $this->isAllowed($parent, $privileges))) return $res;
		}
		
		# if allow all or deny all
		if(is_bool($this->_rules[$roleId])) return $this->_rules[$roleId];
		
		# check privileges
		if(!is_array($privileges)) $privileges = array($privileges);
		foreach($privileges as $privilege){
			if(!isset($this->_rules[$roleId][$privilege])){
				if(true !== $this->_allowOnNotFoundPrivilege) return $this->_allowOnNotFoundPrivilege;
			}
		}
		
		return true;
	}
	
	/**
	 * 
	 * @param CF_Acl_Role|array $roles
	 * @param string|array $privileges
	 * @return boolean
	 * @throws CF_Acl_Exception
	 */
	public function isAllowed($roles, $privileges = null)
	{
		if(!is_array($roles)) $roles = array($roles);
		
		# if privileges is null, check all privileges
		if(null === $privileges) $privileges = $this->_privileges;
		
		foreach($roles as $role){
			$roleId = (string) $role;
			$role = $this->_acl->getRole($roleId);
			
			if(null === $role){
				if(CF::TYPE_EXCEPTION === $this->_acl->getAllowOnNotFound()){
					throw new CF_Acl_Exception("Role \"$role\" not found");
				}
				
				if(true !== ($res = $this->_acl->getAllowOnNotFound())) return $res;
			}
			
			# if return not true, check parent role
			if(true !== ($res = $this->_isAllowed($role, $privileges))){
				$parents = $role->getAllParents();
				if(empty($parents)) return $res;
				if(true !== ($res = $this->isAllowed($parents, $privileges))) return $res; 
			}
		}
		
		return true;
	}
	
	/**
	 * @return string
	 */
	public function __toString()
	{
		return $this->getId();
	}
}